<?php

namespace App\Exceptions;

use Exception;
use App\Http\Responses\RespondsJson;
use Illuminate\Http\Request;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    use RespondsJson;

    /**
     * A list of the exception types that should not be reported.
     *
     * @var array
     */
    protected $dontReport = [
        AuthorizationException::class,
        HttpException::class,
        ModelNotFoundException::class,
        ValidationException::class,
    ];

    /**
     * A list of the exceptions that should not be exposed.
     * Instead, another exception message will substitute.
     *
     * NOTE: currently, this only works in json response.
     * @var array
     */
    private $wontExpose = [
        TokenMismatchException::class => '请勿重复提交',
    ];

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception  $e
     * @return void
     */
    public function report(Exception $e)
    {
        parent::report($e);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $ex
     *
     * @return $this|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
     */
    public function render($request, Exception $ex)
    {
        if ($this->shouldRenderAsJson($request)) {
            return $this->renderAsJson($request, $ex);
        }

        if ($ex instanceof ValidationException) {
            return $this->renderAsJson($request, $ex);
            //return redirect()->back()->withInput()->withErrors($ex->validator->errors());
        }
        if ($ex instanceof \EasyWeChat\Core\Exceptions\FaultException) {
            return $this->renderAsJson($request, $ex);
            //return redirect()->back()->withInput()->withErrors($ex->validator->errors());
        }


        return parent::render($request, $ex);
    }

    /**
     * check whether JSON should be rendered for given request
     *
     * @param Request $request  http request
     * @return bool             true if JSON is requested to respond. false otherwise.
     */
    private function shouldRenderAsJson(Request $request)
    {
        // if request is XMLHttpRequest or JSON is explicitly requested
        // render the exception as JSON
        return $request->ajax() || $request->wantsJson();
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $ex
     *
     * @return \Illuminate\Http\JsonResponse
     */
    private function renderAsJson(Request $request, Exception $ex)
    {
        // if the exception should not be exposed, shelter it
        $message = $this->findShelteredMessage($ex);
        if ($message != null) {
            return $this->respondJson(new Exception($message, $ex->getCode()));
        }

        return $this->respondJson($this->morphException($ex));
    }

    /**
     * find sheltered message for given exception
     *
     * @param Exception $ex  exception to check
     * @return string|null   sheltered message
     */
    private function findShelteredMessage(Exception $ex)
    {
        foreach ($this->wontExpose as $type => $message) {
            if ($ex instanceof $type) {
                return $message;
            }
        }

        return null;
    }

    /**
     * @param Exception $ex
     * @return Exception
     */
    private function morphException(Exception $ex)
    {
        if ($ex instanceof ValidationException) {
            return $this->morphValidationException($ex);
        }

        return $ex;
    }

    /**
     * morph ValidationException instance to an Excetpion instance
     *
     * @param ValidationException $ex
     * @return Exception
     */
    private function morphValidationException(ValidationException $ex)
    {
        $message = $ex->validator->errors()->first();
        return new Exception($message, $ex->getCode(), $ex);
    }
}
